package com.demo.webapp.kcfinder.spring;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.context.WebApplicationContext;

import com.demo.webapp.kcfinder.ObjectFactory;
import com.demo.webapp.kcfinder.action.Action;
import com.demo.webapp.kcfinder.config.ActionConfig;
import com.demo.webapp.kcfinder.config.InterceptorConfig;
import com.demo.webapp.kcfinder.config.ResultTypeConfig;
import com.demo.webapp.kcfinder.context.ActionContext;
import com.demo.webapp.kcfinder.interceptor.Interceptor;
import com.demo.webapp.kcfinder.result.Result;

/**
 * 对象工厂
 * 该对象用于创建Action、Interceptor、Result等对象
 * 
 * @author ryuu.kk
 *
 */
@SuppressWarnings("rawtypes")
public class SpringBeanFactory extends ObjectFactory implements ApplicationContextAware {

    private static final Log Logger = LogFactory.getLog(SpringBeanFactory.class);
    
    protected AutowireCapableBeanFactory autoWiringFactory;
    
    /** 写入策略 :默认写入策略是不使用自动写入策略,但自动注入注解除外**/
    protected int autowireStrategy = AutowireCapableBeanFactory.AUTOWIRE_NO;
    
    private boolean useClassCache = true;
    private boolean alwaysRespectAutowireStrategy = false;
    private ServletContext servletContext;
	private ApplicationContext applicationContext;
    private final Map<String, Object> classes = new HashMap<String, Object>();
	
    public SpringBeanFactory() {
    }
    
    public void init(ServletContext context) {
    	
    	super.init(context);
    	
    	this.servletContext = super.servletContext;
    	Object rootWebApplicationContext =  this.servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        if(rootWebApplicationContext instanceof RuntimeException){
            RuntimeException runtimeException = (RuntimeException)rootWebApplicationContext;
            Logger.fatal(runtimeException.getMessage());
            return;
        }

        ApplicationContext appContext = (ApplicationContext) rootWebApplicationContext;
        if (appContext == null) {
            // uh oh! looks like the lifecycle listener wasn't installed. Let's inform the user
            String message = "********** FATAL ERROR STARTING UP STRUTS-SPRING INTEGRATION **********\n" +
                    "Looks like the Spring listener was not configured for your web app! \n" +
                    "Nothing will work until WebApplicationContextUtils returns a valid ApplicationContext.\n" +
                    "You might need to add the following to web.xml: \n" +
                    "    <listener>\n" +
                    "        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>\n" +
                    "    </listener>";
            Logger.fatal(message);
            return;
        }
        applicationContext = appContext;
    }
	@Override
	public Object buildBean(String beanName, Map extraContext, boolean injectInternal) throws Exception {
    	
       Object instance;
        if (applicationContext.containsBean(beanName)) {
        	instance = applicationContext.getBean(beanName);
        } else {
            Class<?> beanClazz = getClassInstance(beanName);
            instance = buildBean(beanClazz, extraContext);
        }
        //使用注解进行判断
        return instance;
    }
    
	@Override
    public Object buildBean(Class clazz, Map extraContext) throws Exception {
    	
		Object bean;
		
		if (autoWiringFactory == null) {
			autoWiringFactory = findAutoWiringBeanFactory(this.applicationContext);
		}
        try {
            // Decide to follow autowire strategy or use the legacy approach which mixes injection strategies
            if (alwaysRespectAutowireStrategy) {
                // Leave the creation up to Spring
                bean = autoWiringFactory.createBean(clazz, autowireStrategy, false);
                return bean;
            } else {
                bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
                bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName());
                // We don't need to call the init-method since one won't be registered.
                bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName());
                return autoWireBean(bean, autoWiringFactory);
            }
        } catch (UnsatisfiedDependencyException e) {
            if (Logger.isErrorEnabled())
            	Logger.error("Error building bean", e);
            // Fall back
            return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory);
        }
    }
    
    public Action buildAction(ActionConfig actionConfig, ActionContext context) throws Exception {
    	Action action = (Action) buildBean(actionConfig.getActionClass(), context.getParameters(), true);
    	if (ApplicationContextAware.class.isAssignableFrom(action.getClass())) {
    		((ApplicationContextAware) action).setApplicationContext(applicationContext);
    	}
    	return action;
    }
    
    public Interceptor buildInterceptor(InterceptorConfig config) throws Exception {
    	Interceptor interceptor = (Interceptor) buildBean(config.getClassName(), config.getParams(), true);
    	if (ApplicationContextAware.class.isAssignableFrom(interceptor.getClass())) {
    		((ApplicationContextAware) interceptor).setApplicationContext(applicationContext);
    	}
    	return interceptor;
    }

    public Result buildResult(ResultTypeConfig config) throws Exception {
    	Result result = (Result) buildBean(config.getClassName(), config.getParams(), true);
    	if (ApplicationContextAware.class.isAssignableFrom(result.getClass())) {
    		((ApplicationContextAware) result).setApplicationContext(applicationContext);
    	}
    	return result;
    }
    
    public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) {
        if (autoWiringFactory != null) {
            autoWiringFactory.autowireBeanProperties(bean, autowireStrategy, false);
        }
        return bean;
    }
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
		autoWiringFactory = findAutoWiringBeanFactory(this.applicationContext);
	}
	
    public Object autoWireBean(Object bean) {
        return autoWireBean(bean, autoWiringFactory);
    }
    
    protected AutowireCapableBeanFactory findAutoWiringBeanFactory(ApplicationContext context) {
        if (context instanceof AutowireCapableBeanFactory) {
            // Check the context
            return (AutowireCapableBeanFactory) context;
        } else if (context instanceof ConfigurableApplicationContext) {
            //try and grab the beanFactory
            return ((ConfigurableApplicationContext) context).getBeanFactory();
        } else if (context.getParent() != null) {
            // And if all else fails, try again with the parent context
            return findAutoWiringBeanFactory(context.getParent());
        }
        return null;
    }
    
    public void setAutowireStrategy(int autowireStrategy) {
        switch (autowireStrategy) {
            case AutowireCapableBeanFactory.AUTOWIRE_BY_NAME:
                if (Logger.isInfoEnabled()) {
                	Logger.info("Setting autowire strategy to name");
                }
                this.autowireStrategy = autowireStrategy;
                break;
            case AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE:
                if (Logger.isInfoEnabled()) {
                	Logger.info("Setting autowire strategy to type");
                }
                this.autowireStrategy = autowireStrategy;
                break;
            case AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR:
                if (Logger.isInfoEnabled()) {
                	Logger.info("Setting autowire strategy to constructor");
                }
                this.autowireStrategy = autowireStrategy;
                break;
            case AutowireCapableBeanFactory.AUTOWIRE_NO:
                if (Logger.isInfoEnabled()) {
                	Logger.info("Setting autowire strategy to none");
                }
                this.autowireStrategy = autowireStrategy;
                break;
            default:
                throw new IllegalStateException("Invalid autowire type set");
        }
    }
    
    @Override
	protected Class<?> getClassInstance(String className) throws ClassNotFoundException {
    	Class<?> clazz = null;
		if (useClassCache) {
			synchronized (classes) {
				// this cache of classes is needed because Spring sucks at
				// dealing with situations where the
				// class instance changes
				clazz = (Class<?>) classes.get(className);
			}
		}

		if (clazz == null) {
			if (applicationContext.containsBean(className)) {
				clazz = applicationContext.getBean(className).getClass();
			} else {
				clazz = super.getClassInstance(className);
			}

			if (useClassCache) {
				synchronized (classes) {
					classes.put(className, clazz);
				}
			}
		}

		return clazz;
	}
    
    public void setUseClassCache(boolean useClassCache) {
        this.useClassCache = useClassCache;
    }

	public void setAlwaysRespectAutowireStrategy(boolean alwaysRespectAutowireStrategy) {
		this.alwaysRespectAutowireStrategy = alwaysRespectAutowireStrategy;
	}
}
